// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

/*
Package mail implements parsing of mail messages.

For the most part, this package follows the syntax as specified by RFC 5322 and
extended by RFC 6532.
Notable divergences:
	* Obsolete address formats are not parsed, including addresses with
	  embedded route information.
	* The full range of spacing (the CFWS syntax element) is not supported,
	  such as breaking addresses across lines.
	* No unicode normalization is performed.
	* The special characters ()[]:;@\, are allowed to appear unquoted in names.
*/
package mail

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"log"
	"mime"
	"net/textproto"
	"strings"
	"sync"
	"time"
	"unicode/utf8"
)

var debug = debugT(false)

type debugT bool

func (d debugT) Printf(format string, args ...interface{}) {
	if d {
		log.Printf(format, args...)
	}
}

// 消息表示已解析的邮件消息。
type Message struct {
	Header Header
	Body   io.Reader
}

// ReadMessage从r读取消息。
// 已解析邮件头，邮件正文将可用
// 从msg.body读取。
func ReadMessage(r io.Reader) (msg *Message, err error) {
	tp := textproto.NewReader(bufio.NewReader(r))

	hdr, err := tp.ReadMIMEHeader()
	if err != nil {
		return nil, err
	}

	return &Message{
		Header: Header(hdr),
		Body:   tp.R,
	}, nil
}

// 适合传递给time.Parse的布局。
// 这些都按顺序进行了试验。
var (
	dateLayoutsBuildOnce sync.Once
	dateLayouts          []string
)

func buildDateLayouts() {
	// 根据RFC 5322第3.3节生成布局。

	dows := [...]string{"", "Mon, "}   // 星期几
	days := [...]string{"2", "02"}     // 天=1*2数字
	years := [...]string{"2006", "06"} // 年=4位/2位
	seconds := [...]string{":05", ""}  // 第二个
	// /“-0700（MST）”不在RFC 5322中，但很常见。
	zones := [...]string{"-0700", "MST"} // 区域=（（“+”/“-”）4数字）/“格林威治标准时间”/。。。

	for _, dow := range dows {
		for _, day := range days {
			for _, year := range years {
				for _, second := range seconds {
					for _, zone := range zones {
						s := dow + day + " Jan " + year + " 15:04" + second + " " + zone
						dateLayouts = append(dateLayouts, s)
					}
				}
			}
		}
	}
}

// ParseDate解析RFC 5322日期字符串。
func ParseDate(date string) (time.Time, error) {
	dateLayoutsBuildOnce.Do(buildDateLayouts)
	// CR和LF必须匹配，并且在日期字段的任何位置都可以容忍。
	date = strings.ReplaceAll(date, "\r\n", "")
	if strings.Index(date, "\r") != -1 {
		return time.Time{}, errors.New("mail: header has a CR without LF")
	}
	// 我们正在使用一些支持过时文本的addrParser方法，即不可打印的ASCII 
	p := addrParser{date, nil}
	p.skipSpace()

	// RFC 5322:zone=（FWS（“+”/“-”）4DIGIT）/obs zone 
	// 除非过时，否则分区长度始终为5个字符（obs zone）在这种情况下，我们有以下日期格式：1997年11月20日星期四09:55:06 MDT 
	if ind := strings.IndexAny(p.s, "+-"); ind != -1 && len(p.s) >= ind+5 {
		date = p.s[:ind+5]
		p.s = p.s[ind+5:]
	} else {
		ind := strings.Index(p.s, "T")
		if ind == 0 {
			// /*Thu，1997年11月20日09:55:06 MDT（MDT）
			// /*Thu，1997年11月20日09:55:06 MDT（此评论）
			ind = strings.Index(p.s[1:], "T")
			if ind != -1 {
				ind++
			}
		}

		if ind != -1 && len(p.s) >= ind+5 {
			// 当找不到标准时区时，检查过时时区的最后一个字母T。
			// 如果T放错了位置，则要解析的日期是垃圾。
			date = p.s[:ind+1]
			p.s = p.s[ind+1:]
		}
	}
	if !p.skipCFWS() {
		return time.Time{}, errors.New("mail: misformatted parenthetical comment")
	}
	for _, layout := range dateLayouts {
		t, err := time.Parse(layout, date)
		if err == nil {
			return t, nil
		}
	}
	return time.Time{}, errors.New("mail: header could not be parsed")
}

// 头表示邮件消息头中的键值对。
type Header map[string][]string

// Get获取与给定键关联的第一个值。
// 不区分大小写；CanonicalTimeHeaderKey用于
// 规范化提供的密钥。
// 如果没有与键关联的值，Get将返回“”。
// 要访问一个键的多个值，或使用非规范键，
// 直接访问映射。
func (h Header) Get(key string) string {
	return textproto.MIMEHeader(h).Get(key)
}

var ErrHeaderNotPresent = errors.New("mail: header not in message")

// Date解析日期头字段。
func (h Header) Date() (time.Time, error) {
	hdr := h.Get("Date")
	if hdr == "" {
		return time.Time{}, ErrHeaderNotPresent
	}
	return ParseDate(hdr)
}

// AddressList将命名标头字段解析为地址列表。
func (h Header) AddressList(key string) ([]*Address, error) {
	hdr := h.Get(key)
	if hdr == "" {
		return nil, ErrHeaderNotPresent
	}
	return ParseAddressList(hdr)
}

// 地址表示单个邮件地址。
// 像“巴里·吉布斯”这样的地址<bg@example.com>
// 表示为地址{Name:“Barry Gibbs”，地址：bg@example.com"}. 
type Address struct {
	Name    string // 专有名称；可能是空的。
	Address string // user@domain
}

// ParseAddress解析单个RFC 5322地址，例如“Barry Gibbs”<bg@example.com>“
func ParseAddress(address string) (*Address, error) {
	return (&addrParser{s: address}).parseSingleAddress()
}

// ParseAddressList将给定字符串解析为地址列表。
func ParseAddressList(list string) ([]*Address, error) {
	return (&addrParser{s: list}).parseAddressList()
}

// 地址解析器是RFC 5322地址解析器。
type AddressParser struct {
	// WordDecoder可选地指定RFC 2047编码字的解码器。
	WordDecoder *mime.WordDecoder
}

// Parse解析
// 表单“Gogh Fir”的单个RFC 5322地址<gf@example.com>“或”foo@example.com". 
func (p *AddressParser) Parse(address string) (*Address, error) {
	return (&addrParser{s: address, dec: p.WordDecoder}).parseSingleAddress()
}

// ParseList将给定字符串解析为逗号分隔的地址列表，格式为“Gogh Fir”<gf@example.com>“或”foo@example.com". 
func (p *AddressParser) ParseList(list string) ([]*Address, error) {
	return (&addrParser{s: list, dec: p.WordDecoder}).parseAddressList()
}

// 字符串将地址格式化为有效的RFC 5322地址。
// 如果地址名称包含非ASCII字符
// 将根据RFC 2047呈现名称。
func (a *Address) String() string {
	// 格式地址local@domain
	at := strings.LastIndex(a.Address, "@")
	var local, domain string
	if at < 0 {
		// 这是一个格式错误的地址（addr spec中需要“@”）；
		// 将整个地址视为本地部分。
		local = a.Address
	} else {
		local, domain = a.Address[:at], a.Address[at+1:]
	}

	// 如果需要添加引号
	quoteLocal := false
	for i, r := range local {
		if isAtext(r, false, false) {
			continue
		}
		if r == '.' {
			// 如果圆点周围有文字，则可以添加引号。
			// 我们只需要检查前一个字节是否为
			// 不是一个点，这不是字符串的结尾。
			if i > 0 && local[i-1] != '.' && i < len(local)-1 {
				continue
			}
		}
		quoteLocal = true
		break
	}
	if quoteLocal {
		local = quoteString(local)

	}

	s := "<" + local + "@" + domain + ">"

	if a.Name == "" {
		return s
	}

	// 如果每个字符都是可打印的ASCII码，则引号很简单。
	allPrintable := true
	for _, r := range a.Name {
		// 这里的isWSP实际上应该是isFWS，
		// 但我们还不支持折叠。
		if !isVchar(r) && !isWSP(r) || isMultibyte(r) {
			allPrintable = false
			break
		}
	}
	if allPrintable {
		return quoteString(a.Name) + " " + s
	}

	// 显示名称中的编码字中的文本不得包含某些
	// 字符，如引号或括号（参见RFC 2047第5.3节）。
	// 在这种情况下，使用base64编码对名称进行编码。
	if strings.ContainsAny(a.Name, "\"#$%&'(),.:;<>@[]^`{|}~") {
		return mime.BEncoding.Encode("utf-8", a.Name) + " " + s
	}
	return mime.QEncoding.Encode("utf-8", a.Name) + " " + s
}

type addrParser struct {
	s   string
	dec *mime.WordDecoder // 可能为零
}

func (p *addrParser) parseAddressList() ([]*Address, error) {
	var list []*Address
	for {
		p.skipSpace()

		// 允许跳过空条目（RFC5322 obs地址列表）
		if p.consume(',') {
			continue
		}

		addrs, err := p.parseAddress(true)
		if err != nil {
			return nil, err
		}
		list = append(list, addrs...)

		if !p.skipCFWS() {
			return nil, errors.New("mail: misformatted parenthetical comment")
		}
		if p.empty() {
			break
		}
		if p.peek() != ',' {
			return nil, errors.New("mail: expected comma")
		}

		// 跳过obs地址列表的空条目。
		for p.consume(',') {
			p.skipSpace()
		}
		if p.empty() {
			break
		}
	}
	return list, nil
}

func (p *addrParser) parseSingleAddress() (*Address, error) {
	addrs, err := p.parseAddress(true)
	if err != nil {
		return nil, err
	}
	if !p.skipCFWS() {
		return nil, errors.New("mail: misformatted parenthetical comment")
	}
	if !p.empty() {
		return nil, fmt.Errorf("mail: expected single address, got %q", p.s)
	}
	if len(addrs) == 0 {
		return nil, errors.New("mail: empty group")
	}
	if len(addrs) > 1 {
		return nil, errors.New("mail: group with multiple addresses")
	}
	return addrs[0], nil
}

// parseAddress解析p开头的单个RFC 5322地址。
func (p *addrParser) parseAddress(handleGroup bool) ([]*Address, error) {
	debug.Printf("parseAddress: %q", p.s)
	p.skipSpace()
	if p.empty() {
		return nil, errors.New("mail: no address")
	}

	// address=mailbox/group 
	// mailbox=name addr/addr spec 
	// group=display name:“[group list]”；“[CFWS]

	// addr spec的语法比name addr、
	// 所以首先尝试解析它，然后回退到name addr。这真的正确吗？
	spec, err := p.consumeAddrSpec()
	if err == nil {
		var displayName string
		p.skipSpace()
		if !p.empty() && p.peek() == '(' {
			displayName, err = p.consumeDisplayNameComment()
			if err != nil {
				return nil, err
			}
		}

		return []*Address{{
			Name:    displayName,
			Address: spec,
		}}, err
	}
	debug.Printf("parseAddress: not an addr-spec: %v", err)
	debug.Printf("parseAddress: state is now %q", p.s)

	// 显示名称
	var displayName string
	if p.peek() != '<' {
		displayName, err = p.consumePhrase()
		if err != nil {
			return nil, err
		}
	}
	debug.Printf("parseAddress: displayName=%q", displayName)

	p.skipSpace()
	if handleGroup {
		if p.consume(':') {
			return p.consumeGroupList()
		}
	}
	// angle addr=“<”addr spec“>”
	if !p.consume('<') {
		atext := true
		for _, r := range displayName {
			if !isAtext(r, true, false) {
				atext = false
				break
			}
		}
		if atext {
			// 输入类似“foo.bar”；输入
			// 可能是“foo”。bar@domain，或“foo.bar<…>”。
			return nil, errors.New("mail: missing '@' or angle-addr")
		}
		// 输入类似于“全名”，如果后跟“@domain”，则不可能是
		// 有效的电子邮件地址；输入
		// 可能是“全名<…>”。
		return nil, errors.New("mail: no angle-addr")
	}
	spec, err = p.consumeAddrSpec()
	if err != nil {
		return nil, err
	}
	if !p.consume('>') {
		return nil, errors.New("mail: unclosed angle-addr")
	}
	debug.Printf("parseAddress: spec=%q", spec)

	return []*Address{{
		Name:    displayName,
		Address: spec,
	}}, nil
}

func (p *addrParser) consumeGroupList() ([]*Address, error) {
	var group []*Address
	// 处理空组。
	p.skipSpace()
	if p.consume(';') {
		p.skipCFWS()
		return group, nil
	}

	for {
		p.skipSpace()
		// 不允许嵌入组。
		addrs, err := p.parseAddress(false)
		if err != nil {
			return nil, err
		}
		group = append(group, addrs...)

		if !p.skipCFWS() {
			return nil, errors.New("mail: misformatted parenthetical comment")
		}
		if p.consume(';') {
			p.skipCFWS()
			break
		}
		if !p.consume(',') {
			return nil, errors.New("mail: expected comma")
		}
	}
	return group, nil
}

// ConsumerAddrSpec在第页开头解析单个RFC 5322地址规范。
func (p *addrParser) consumeAddrSpec() (spec string, err error) {
	debug.Printf("consumeAddrSpec: %q", p.s)

	orig := *p
	defer func() {
		if err != nil {
			*p = orig
		}
	}()

	// local part=dot atom/引用字符串
	var localPart string
	p.skipSpace()
	if p.empty() {
		return "", errors.New("mail: no addr-spec")
	}
	if p.peek() == '"' {
		// 引用字符串
		debug.Printf("consumeAddrSpec: parsing quoted-string")
		localPart, err = p.consumeQuotedString()
		if localPart == "" {
			err = errors.New("mail: empty quoted string in addr-spec")
		}
	} else {
		// dot atom 
		debug.Printf("consumeAddrSpec: parsing dot-atom")
		localPart, err = p.consumeAtom(true, false)
	}
	if err != nil {
		debug.Printf("consumeAddrSpec: failed: %v", err)
		return "", err
	}

	if !p.consume('@') {
		return "", errors.New("mail: missing @ in addr-spec")
	}

	// domain=dot atom/domain literal 
	var domain string
	p.skipSpace()
	if p.empty() {
		return "", errors.New("mail: no domain in addr-spec")
	}
	// TODO（dsymonds）：Handle domain literal 
	domain, err = p.consumeAtom(true, false)
	if err != nil {
		return "", err
	}

	return localPart + "@" + domain, nil
}

// consumerphrase在p开头解析RFC 5322短语。
func (p *addrParser) consumePhrase() (phrase string, err error) {
	debug.Printf("consumePhrase: [%s]", p.s)
	// phrase=1*word 
	var words []string
	var isPrevEncoded bool
	for {
		// word=atom/带引号的字符串
		var word string
		p.skipSpace()
		if p.empty() {
			break
		}
		isEncoded := false
		if p.peek() == '"' {
			// 带引号的字符串
			word, err = p.consumeQuotedString()
		} else {
			// atom 
			// 我们实际上在这里解析dot-atom比RFC 5322指定的更为宽松。
			word, err = p.consumeAtom(true, true)
			if err == nil {
				word, isEncoded, err = p.decodeRFC2047Word(word)
			}
		}

		if err != nil {
			break
		}
		debug.Printf("consumePhrase: consumed %q", word)
		if isPrevEncoded && isEncoded {
			words[len(words)-1] += word
		} else {
			words = append(words, word)
		}
		isPrevEncoded = isEncoded
	}
	// 如果我们得到至少一个单词，请忽略任何错误。
	if err != nil && len(words) == 0 {
		debug.Printf("consumePhrase: hit err: %v", err)
		return "", fmt.Errorf("mail: missing word in phrase: %v", err)
	}
	phrase = strings.Join(words, " ")
	return phrase, nil
}

// consumeQuotedString解析p开头的带引号的字符串。
func (p *addrParser) consumeQuotedString() (qs string, err error) {
	// 假设第一个字节是''.
	i := 1
	qsb := make([]rune, 0, 10)

	escaped := false

Loop:
	for {
		r, size := utf8.DecodeRuneInString(p.s[i:])

		switch {
		case size == 0:
			return "", errors.New("mail: unclosed quoted-string")

		case size == 1 && r == utf8.RuneError:
			return "", fmt.Errorf("mail: invalid utf-8 in quoted-string: %q", p.s)

		case escaped:
			// quoted pair=（“\”（VCHAR/WSP））

			if !isVchar(r) && !isWSP(r) {
				return "", fmt.Errorf("mail: bad character in quoted-string: %q", r)
			}

			qsb = append(qsb, r)
			escaped = false

		case isQtext(r) || isWSP(r):
			// qtext（可打印的US-ASCII排除“and”），或
			// FWS（几乎；我们忽略CRLF）
			qsb = append(qsb, r)

		case r == '"':
			break Loop

		case r == '\\':
			escaped = true

		default:
			return "", fmt.Errorf("mail: bad character in quoted-string: %q", r)

		}

		i += size
	}
	p.s = p.s[i+1:]
	return string(qsb), nil
}

// Consumeratom在p开头解析RFC 5322原子。
// 如果dot为true，则consumeAtom将解析RFC 5322 dot原子。
// 如果permissive为true，consumeAtom将不会失败：
// /-原子中的前导/尾随/双点（见golang.org/issue/4938）
// /-特殊字符（RFC 5322 3.2.3），除了“<”、“>”、：”和“”（见golang.org/issue/21018）
func (p *addrParser) consumeAtom(dot bool, permissive bool) (atom string, err error) {
	i := 0

Loop:
	for {
		r, size := utf8.DecodeRuneInString(p.s[i:])
		switch {
		case size == 1 && r == utf8.RuneError:
			return "", fmt.Errorf("mail: invalid utf-8 in address: %q", p.s)

		case size == 0 || !isAtext(r, dot, permissive):
			break Loop

		default:
			i += size

		}
	}

	if i == 0 {
		return "", errors.New("mail: invalid string")
	}
	atom, p.s = p.s[:i], p.s[i:]
	if !permissive {
		if strings.HasPrefix(atom, ".") {
			return "", errors.New("mail: leading dot in atom")
		}
		if strings.Contains(atom, "..") {
			return "", errors.New("mail: double dot in atom")
		}
		if strings.HasSuffix(atom, ".") {
			return "", errors.New("mail: trailing dot in atom")
		}
	}
	return atom, nil
}

func (p *addrParser) consumeDisplayNameComment() (string, error) {
	if !p.consume('(') {
		return "", errors.New("mail: comment does not start with (")
	}
	comment, ok := p.consumeComment()
	if !ok {
		return "", errors.New("mail: misformatted parenthetical comment")
	}

	words := strings.FieldsFunc(comment, func(r rune) bool { return r == ' ' || r == '\t' })
	for idx, word := range words {
		decoded, isEncoded, err := p.decodeRFC2047Word(word)
		if err != nil {
			return "", err
		}
		if isEncoded {
			words[idx] = decoded
		}
	}

	return strings.Join(words, " "), nil
}

func (p *addrParser) consume(c byte) bool {
	if p.empty() || p.peek() != c {
		return false
	}
	p.s = p.s[1:]
	return true
}

// skipSpace跳过前导空格和制表符。
func (p *addrParser) skipSpace() {
	p.s = strings.TrimLeft(p.s, " \t")
}

func (p *addrParser) peek() byte {
	return p.s[0]
}

func (p *addrParser) empty() bool {
	return p.len() == 0
}

func (p *addrParser) len() int {
	return len(p.s)
}

// skipCFWS跳过RFC5322中定义的CFW。
func (p *addrParser) skipCFWS() bool {
	p.skipSpace()

	for {
		if !p.consume('(') {
			break
		}

		if _, ok := p.consumeComment(); !ok {
			return false
		}

		p.skipSpace()
	}

	return true
}

func (p *addrParser) consumeComment() (string, bool) {
	// （'已使用。
	depth := 1

	var comment string
	for {
		if p.empty() || depth == 0 {
			break
		}

		if p.peek() == '\\' && p.len() > 1 {
			p.s = p.s[1:]
		} else if p.peek() == '(' {
			depth++
		} else if p.peek() == ')' {
			depth--
		}
		if depth > 0 {
			comment += p.s[:1]
		}
		p.s = p.s[1:]
	}

	return comment, depth == 0
}

func (p *addrParser) decodeRFC2047Word(s string) (word string, isEncoded bool, err error) {
	if p.dec != nil {
		word, err = p.dec.Decode(s)
	} else {
		word, err = rfc2047Decoder.Decode(s)
	}

	if err == nil {
		return word, true, nil
	}

	if _, ok := err.(charsetError); ok {
		return s, true, err
	}

	// 忽略无效的RFC 2047编码字错误。
	return s, false, nil
}

var rfc2047Decoder = mime.WordDecoder{
	CharsetReader: func(charset string, input io.Reader) (io.Reader, error) {
		return nil, charsetError(charset)
	},
}

type charsetError string

func (e charsetError) Error() string {
	return fmt.Sprintf("charset not supported: %q", string(e))
}

// isAtext报告r是否为RFC 5322 atext字符。
// 如果点为真，则包含句点。
// 如果Permission为真，则包含RFC 5322 3.2.3特殊值，
// 除了“<”、“>”、“：”和““.
func isAtext(r rune, dot, permissive bool) bool {
	switch r {
	case '.':
		return dot

	// RFC 5322 3.2.3.specials 
	case '(', ')', '[', ']', ';', '@', '\\', ',':
		return permissive

	case '<', '>', '"', ':':
		return false
	}
	return isVchar(r)
}

// isQtext报告r是否为RFC 5322 qtext字符。
func isQtext(r rune) bool {
	// 可打印的US-ASCII，不包括反斜杠或引号。
	if r == '\\' || r == '"' {
		return false
	}
	return isVchar(r)
}

// quoteString将字符串呈现为RFC 5322引号字符串。
func quoteString(s string) string {
	var buf strings.Builder
	buf.WriteByte('"')
	for _, r := range s {
		if isQtext(r) || isWSP(r) {
			buf.WriteRune(r)
		} else if isVchar(r) {
			buf.WriteByte('\\')
			buf.WriteRune(r)
		}
	}
	buf.WriteByte('"')
	return buf.String()
}

// isVchar报告r是否为RFC 5322 VCHAR character.
func isVchar(r rune) bool {
	// 可见（打印）字符。
	return '!' <= r && r <= '~' || isMultibyte(r)
}

// isMultibyte报告r是否为多字节UTF-8字符
// 受RFC 6532 
func isMultibyte(r rune) bool {
	return r >= utf8.RuneSelf
}

// isWSP支持，报告r是否为WSP（空白）。
// WSP为空格或水平制表符（RFC 5234附录B）。
func isWSP(r rune) bool {
	return r == ' ' || r == '\t'
}
